package com.youxin.busfoundation.utils.mybatis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import com.youxin.foundation.utils.RegexUtils;

@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class,Integer.class})})  
public class PagePlugin implements Interceptor {

	private static String dialect;
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		 RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();  
         StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");  
         BoundSql boundSql = delegate.getBoundSql();  
         Object parameterObject = boundSql.getParameterObject(); 
        
         if(parameterObject != null && parameterObject instanceof BasePage<?>){
        	 
	         BasePage<?> page = (BasePage<?>)parameterObject ;
	         if (page != null) {  
	             MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");  
	             Connection connection = (Connection)invocation.getArgs()[0];  
	             String sql = boundSql.getSql();  
	             this.setTotalRecord(page, mappedStatement, connection,parameterObject);  
	             String pageSql = this.getPageSql(page, sql);  
	             ReflectUtil.setFieldValue(boundSql, "sql", pageSql);  
	         }  
         }
         return invocation.proceed();  
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		PagePlugin.dialect = properties.getProperty("dialect");
	} 
	
	private String getPageSql(BasePage<?> page, String sql) {  
        StringBuffer sqlBuffer = new StringBuffer(sql);  
        if ("mysql".equalsIgnoreCase(PagePlugin.dialect)) {  
            return getMysqlPageSql(page, sqlBuffer);  
        } else if ("oracle".equalsIgnoreCase(PagePlugin.dialect)) {  
            return getOraclePageSql(page, sqlBuffer);  
        }  
        return sqlBuffer.toString();  
     }  
	
	private String getMysqlPageSql(BasePage<?> page, StringBuffer sqlBuffer) {  
        int offset = (page.getPageNo()-1) * page.getPageSize();  
        sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());  
        return sqlBuffer.toString();  
     } 
	
	private String getOraclePageSql(BasePage<?> page, StringBuffer sqlBuffer) {  
        int offset = (page.getPageNo()-1) * page.getPageSize() + 1;  
        sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());  
        sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);  
        //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16  
        return sqlBuffer.toString();  
     }  
	
	private void setTotalRecord(BasePage<?> page,  
            MappedStatement mappedStatement, Connection connection,Object parameterObject) {  
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);  
        String sql = boundSql.getSql();  
        String countSql = this.getCountSql(sql);  
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
        
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);  
        PreparedStatement pstmt = null;  
        ResultSet rs = null;  
        try {  
            pstmt = connection.prepareStatement(countSql);  
            parameterHandler.setParameters(pstmt);  
            rs = pstmt.executeQuery();  
            if (rs.next()) {  
               long totalRecord = rs.getLong(1);  
               page.setTotalRecord(totalRecord);  
            }  
        } catch (SQLException e) {  
            e.printStackTrace();  
        } finally {  
            try {  
               if (rs != null)  
                   rs.close();  
                if (pstmt != null)  
                   pstmt.close();  
            } catch (SQLException e) {  
               e.printStackTrace();  
            }  
        }  
     } 
	
	private String getCountSql(String sql) {  		
	   String sqlString=sql.toLowerCase();		
	   Matcher tmatcher=RegexUtils.match("\\s+(from)\\s+", sqlString,Pattern.MULTILINE, (matcher, success) -> {      	
        	return matcher;
        });
	   if(tmatcher.find())
	   {
	        int index=tmatcher.start(1);   
	        sqlString=sqlString.substring(index); 	        
	        tmatcher=RegexUtils.match("\\s+(order\\s+by)\\s+", sqlString,Pattern.MULTILINE, (matcher, success) -> {      	
	        	return matcher;
	        });
	        
	        if(tmatcher.find())
	        {
	        	sqlString=sqlString.substring(0, tmatcher.start(1));
	        }    
	   }
          
              
        
        return "select count(*) " +   sqlString;
     }  
	
}
